/* SQUIB - Scala's Quirky User Interface Builder 
    Copyright (C) 2008 Tim Dalton

    This library is free software you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
*/

package tfd.scala.squib.demo.scenario

import java.awt.{BasicStroke, BorderLayout, Color, Font, Point, Rectangle, RenderingHints, Shape}
import java.awt.event._
import java.awt.geom.{Arc2D, Ellipse2D, Point2D, RoundRectangle2D}
import java.awt.image._
import javax.imageio._
import javax.swing.{JFrame, UIManager}
import javax.swing.border._
import javax.swing.SwingConstants._
import javax.swing.event._

import com.sun.scenario.scenegraph._
import com.sun.scenario.scenegraph.event._
import com.sun.scenario.animation._

import tfd.scala.squib._
import tfd.scala.squib.event.{sliderValueChanged}
import tfd.scala.squib.scenario.scenegraph._
import tfd.scala.squib.scenario.scenegraph.event.{mouseDragged}
import tfd.scala.squib.scenario.scenegraph.Implicits._

object Intro extends Application {

    def createFill(shape2D: Shape)  
     =  shape(
              'shape -> shape2D,
              'fillPaint -> Color.black,
              'mode -> SGAbstractShape.Mode.FILL,
              'antialiasingHint -> RenderingHints.VALUE_ANTIALIAS_ON
         )
    
    def createStroke(shape2D: Shape, width:float) 
     =  shape(
             'shape -> shape2D,
             'drawStroke -> new BasicStroke(width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER),
             'drawPaint -> Color.black,
             'mode -> SGAbstractShape.Mode.STROKE,
             'antialiasingHint -> RenderingHints.VALUE_ANTIALIAS_ON
         )
    
    def P(x:Double, y:Double) = new Point2D.Double(x, y)
         
    def arcCenter(r:float, p1:Point2D, p2:Point2D) = {
       // d is the distance between p1,p2 and d2 is the distance
        // to the midpoint of that line.
        val d = p1.distance(p2)
        val d2 = d / 2.0
        // max possible value for d2 is r, when p1,p2 bisect the circle
        if (d2 > r) {
            throw new IllegalArgumentException("invalid parameters")
        }
        // dc is the distance from center of p1,p2 to the circle's 
        // center, since the circle's radius makes a right trangle with
        // dc and d2. 
        val dc = Math.sqrt((r*r) - (d2*d2))
        // x3,y3 are the coordinates of the midpoint of p1,p2 and
        // ux,uy is a unit vector perpindicular to p1,p2
        val x3 = (p1.getX() + p2.getX()) / 2.0f
        val y3 = (p1.getY() + p2.getY()) / 2.0f
        val ux = (p2.getY() - p1.getY()) / d
        val uy = (p1.getX() - p2.getX()) / d
        // The circle's center is just x3,y3 plus the ux,uy
        // scaled by dc (the distance from x3,y3 to the center)
        val cx = x3 + (ux * dc)
        val cy = y3 + (uy * dc)
        new Point2D.Double(cx, cy)
    }
    
    def createBorder(node:SGNode):SGNode = {
        val nodeR = node.getBounds()
        val borderWidth = 10
        val x = nodeR.getX() - borderWidth
        val y = nodeR.getY() - borderWidth
        val w = nodeR.getWidth() + (2 * borderWidth)
        val h = nodeR.getHeight() + (2 * borderWidth)
        val a = 1.5 * borderWidth
        group(
            shape(
                'shape -> new RoundRectangle2D.Double(x, y, w, h, a, a),
                'fillPaint -> new Color(0x660000),
                'drawPaint -> new Color(0xFFFF33),
                'drawStroke -> new BasicStroke(borderWidth / 2),
                'mode -> SGAbstractShape.Mode.STROKE_FILL,
                'antialiasingHint -> RenderingHints.VALUE_ANTIALIAS_ON
            ),
            node
        )
    }
    
    def createArc(r:float, p1:Point2D, p2: Point2D):Shape = {
        val c = arcCenter(r, p1, p2)
        val x1 = p1.getX() - c.getX() // p1 translated to the center
        val y1 = p1.getY() - c.getY()
        val a1 = 360.0 - Math.toDegrees(Math.atan2(y1, x1))
        val x2 = p2.getX() - c.getX() // p2 translated to the center
        val y2 = p2.getY() - c.getY()
        val a2 = 360.0 - Math.toDegrees(Math.atan2(y2, x2))
        val x = (c.getX() - r) // origin of the arc's bounding
        val y = (c.getY() - r) // rectangle
        val s = 2f * r // diameter of the circle that contains the arc
        val a = a1 // arc start angle
        val e = (a2 - a1) // arc angle's "extent"
        new Arc2D.Double(x, y, s, s, a, e, Arc2D.OPEN)
    }
    
    def createEarth() = {
        var earthImage:BufferedImage = null
        try {
            val url = getClass().getResource("earth.png")
            if (url != null) {
                earthImage = ImageIO.read(url)
            }
        } catch  {
            case ex:Exception => System.err.println(ex.toString)
        }
        if (earthImage == null) {
            System.err.println("can't load earth image")
        }
        image('image -> earthImage)
    }
    
    def createHelloWorldText() =  text(
            'text->"Hello World",
            'font->new Font("SansSerif", Font.PLAIN, 36),
            'antialiasingHint -> RenderingHints.VALUE_TEXT_ANTIALIAS_ON,
            'fillPaint -> Color.WHITE
        )

    def createRow(nodes:SGNode*):SGNode = {
        var rowHeight = 0.0
        for (node <- nodes.elements) {
            rowHeight = Math.max(rowHeight, node.getBounds().getHeight())
        }
        var x = 0.0
        val gap = 8.0
        group(
                for (node <- nodes.elements) yield {        
                    val nodeR = node.getBounds()
                    val y = (rowHeight - nodeR.getHeight()) / 2.0
                    val dx = x - nodeR.getX()
                    val dy = y - nodeR.getY()
                    x = x + nodeR.getWidth() + gap
                    translate(dx, dy, node)
                }
        )
    }

    def createRotation(id:String, node:SGNode) = {
        val nodeR = node.getBounds()
        val cx = nodeR.getCenterX()
        val cy = nodeR.getCenterY()
        translate(cx, cy,
                rotate(id, 0.0,
                        translate(-cx, -cy, node)
                )
        )
    }

    def createDraggableEarth() = {
        val earth = createEarth()
        image.events(earth, mouseDragged { (e:MouseEvent, node:SGNode) =>
                val r = e.getComponent().getBounds()
                val x = e.getX() - r.getCenterX()
                val y = e.getY() - r.getCenterY()
                rotate.id("earth").setRotation(Math.atan2(y, x))
        })
         
        createRotation("earth", createBorder(createRow(createHelloWorldText(), earth))) 
    }
    
    def createRotatingHelloWorldPanel() =
        panel(
            'layout -> borderlayout(),
            contents(
                panel(
                    contents(
                        label('text->"Rotation: "),
                        slider(attributes('min -> 0, 'max -> 100, 'value -> 0),
                               events(sliderValueChanged { value:Int => rotate.id("hello").setRotation(value * (java.lang.Math.PI / 50.0))}))
                    )
                ) -> BorderLayout.NORTH,
                scalingpanel(
                    'background -> Color.BLACK,
                    'preferredSize -> dimension(640,480),
                    'scene -> createRotation("hello", createBorder(createHelloWorldText()))
                ) -> BorderLayout.CENTER
            )
        )

    class ScalingSGPanel extends JSGPanel {
        private var root:SGNode = null
        private var originT:SGTransform.Translate = null
        private var scaleT:SGTransform.Scale = null
        private var centerT:SGTransform.Translate = null
        
        val reqFocus = new MouseAdapter() {
            override def mousePressed(e:MouseEvent) =  e.getComponent().requestFocus()
                
        }
        addMouseListener(reqFocus)

        override def setScene(scene:SGNode) = {
            root = scene
            originT = translate(0f, 0f, scene)
            scaleT = scale(1f, 1f, originT)
            centerT = translate(0f, 0f, scaleT)
            super.setScene(centerT)
        }
 
        override def doLayout() = {
            if (root != null) {
                val sceneR = root.getBounds()
                originT.setTranslateX(-sceneR.getX())
                originT.setTranslateY(-sceneR.getY())
                val insets = getInsets()
                val iw = insets.left + insets.right
                val ih = insets.top + insets.bottom
                val size = Math.min(getWidth() - iw, getHeight() - ih)
                val scale = size / Math.max(sceneR.getWidth(), sceneR.getHeight())
                scaleT.setScale(scale, scale)
                val ssw = scale * sceneR.getWidth()
                val ssh = scale * sceneR.getHeight()
                centerT.setTranslateX(insets.left + ((getWidth() - ssw - iw) / 2.0))
                centerT.setTranslateY(insets.top + ((getHeight() - ssh - ih) / 2.0))
            }
        }
    }
    
    // Allow ScalingSGPanel to be a SQUIB component
    object scalingpanel extends BuiltComponent[ScalingSGPanel] {
        def newComponent = new ScalingSGPanel()
    }
    
    try {
      UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
    } catch {
        case e:Exception =>   System.err.println("Error setting native LAF: " + e);
    }
     
    // Since there only one smiley, it can be a lazy val instead of a method
    lazy val smiley = group(
                            shape(// smiley
                                'shape-> new Ellipse2D.Float(-20f, -20f, 40f, 40f),
                                'fillPaint -> Color.YELLOW,
                                'mode -> SGAbstractShape.Mode.STROKE_FILL,
                                'drawStroke -> new BasicStroke(0.15f),
                                'drawPaint -> Color.black,
                                'antialiasingHint -> RenderingHints.VALUE_ANTIALIAS_ON),
                                createFill(new Ellipse2D.Float(-8.5f, -11f, 5f, 8f)),
                                createStroke(createArc(4f, P(-14.6, 5.7), P(-10.6, 2.7)), 0.5f),
                                createFill(new Ellipse2D.Float(3.5f, -11f, 5f, 8f)),
                                createStroke(createArc(4f, P(10.6, 2.7), P(14.6, 5.7)), 0.5f),
                                createStroke(createArc(13.5f, P(-12, 5), P(12, 5)), 0.75f),
                                createStroke(createArc(13f, P(-12, 5), P(12, 5)), 0.75f)
    )
    
    val mainFrame = frame(
                        'title -> "Scenarion API Introduction 1.0",
                        'defaultCloseOperation -> JFrame.EXIT_ON_CLOSE,
                        contents(
                            tabbedpane(
                                contents(
                                    scalingpanel(
                                        'background -> Color.BLACK,
                                        'preferredSize -> dimension(640,480),
                                        'scene -> createHelloWorldText()
                                    ) -> "Hello",
                                    scalingpanel(
                                        'background -> Color.BLACK,
                                        'preferredSize -> dimension(640,480),
                                        'scene -> createBorder(createHelloWorldText())
                                    ) -> "[Hello]",
                                    createRotatingHelloWorldPanel -> "Rotate",
                                    scalingpanel(
                                        'background -> Color.BLACK,
                                        'preferredSize -> dimension(640,480),
                                        'scene -> createBorder(createRow(createHelloWorldText(), createEarth()))
                                    ) -> "Image",
                                    scalingpanel(
                                        'background -> Color.BLACK,
                                        'preferredSize -> dimension(640,480),
                                        'scene -> createDraggableEarth()
                                    ) -> "Mouse",
                                    scalingpanel(
                                        'border -> new EmptyBorder(4, 4, 4, 4),
                                        'preferredSize -> dimension(640,480),
                                        'background -> Color.white,
                                        'scene -> smiley
                                    ) -> "Smiley"
                                ) 
                            )
                        )
                    )
    mainFrame.pack()
    mainFrame.show()
 }